//*****************************************************************************
//
//! \file DrvSYS.c
//! \brief Driver for the SYS Controller.
//! \version v1.0
//! \date 27/03/2012
//! \author Nuvoton
//! \copy
//!
//! Copyright (c)  2012, Nuvoton 
//! All rights reserved.
//*****************************************************************************
#include <stdio.h>
#include "DrvSYS.h"

#define FREQ_24MHZ        24000000
#define FREQ_32MHZ        32000000
#define FREQ_48MHZ        48000000
#define FREQ_96MHZ        96000000
#define FREQ_120MHZ       120000000

//*****************************************************************************
//
//! Global file scope (static) variables
//
//*****************************************************************************
static BOD_CALLBACK BOD_callbackFn;
static PWRWU_CALLBACK PWRWU_callbackFn;

static uint16_t _DrvSYS_PLL_Table[5][6] = {
		// 45MHz,  48MHz,    90MHz,    96MHz,    120MHz,   128MHz 
		{0xFFFF,   0x0110,   0xFFFF,   0x0010,   0x001c,   0xFFFF},		// Input 4MHz 
		{0xFFFF,   0x0210,   0xFFFF,   0x0110,   0xFFFF,   0x0120},		// Input 8MHz 
		{0xFFFF,   0x0200,   0xFFFF,   0x0100,   0x0230,   0xFFFF},		// Input 12MHz 
		{0x0310,   0xFFFF,   0x0210,   0xFFFF,   0x0220,   0xFFFF},		// Input 15MHz 
		{0xFFFF,   0x1320,   0xFFFF,   0x0320,   0x0330,   0xFFFF}		// Input 24MHz 
};

//*****************************************************************************
//!
//! Declare function protypes
//! 
//*****************************************************************************
static void	(*_pfIrcCallback)(uint32_t u32IRCStatus);
static uint32_t g_trimClk;

//*****************************************************************************
//
//! \brief Auto-Trim ISR
//!
//! \param None 
//!
//! \return None  
//
//*****************************************************************************
void HIRC_IRQHandler(void)
{
	uint32_t u32IRCStatus;

	u32IRCStatus = GCR->IRCTRIMISR;
    //
	// Clear the interrupt 
    //
	if (GCR->IRCTRIMISR_BITS.TRIM_FAIL_IS == 1)
		GCR->IRCTRIMISR_BITS.TRIM_FAIL_IS = 1;

	if (GCR->IRCTRIMISR_BITS.ERR_32K_IS == 1)
		GCR->IRCTRIMISR_BITS.ERR_32K_IS = 1;

	GCR->IRCTRIMCTL_BITS.TRIM_LOOP = 3;
	GCR->IRCTRIMCTL_BITS.TRIM_SEL = g_trimClk;
    //
	// Call	the	callback function of IRC interrupt 
    //
	if ( _pfIrcCallback )
		_pfIrcCallback(u32IRCStatus);
}

//*****************************************************************************
//
//! \brief This function is used to get the DrvSYS version number 
//!
//! \param None 
//!
//! \return The DrvSYS version number  
//
//*****************************************************************************
uint32_t DrvSYS_GetVersion(void)
{
	return DRVSYS_VERSION_NUM;
}

//*****************************************************************************
//
//! \brief Read Product ID 
//!
//! \param None 
//!
//! \return Product ID  
//
//*****************************************************************************
uint32_t DrvSYS_ReadProductID(void)
{	
	return GCR->PDID;
}

//*****************************************************************************
//
//! \brief Unlock protected registers 
//!
//! \param None 
//!
//! \return  0      Success 
//!         <0      Failed 
//
//*****************************************************************************
int32_t DrvSYS_UnlockProtectedReg(void)
{
	uint32_t lockbit;

	GCR->RegLockAddr = 0x59;
	GCR->RegLockAddr = 0x16;
	GCR->RegLockAddr = 0x88;

	lockbit = GCR->RegLockAddr & 0x01;
	return (lockbit)? 0 : E_DRVSYS_ERR_UNLOCK_FAIL;
} 

//*****************************************************************************
//
//! \brief Re-Lock protected registers 
//!
//! \param None 
//!
//! \return
//!      0      Success                                                                            
//!     <0      Failed  
//
//*****************************************************************************
int32_t DrvSYS_LockProtectedReg(void)
{
	uint32_t lockbit;

	GCR->RegLockAddr = 0x00;	

	lockbit = GCR->RegLockAddr & 0x01;
	return (lockbit)? E_DRVSYS_ERR_LOCK_FAIL : 0;
}

//*****************************************************************************
//
//! \brief This function is used to check the protected registers are locked or
//!  not 
//!
//! \param None 
//!
//! \return 1: The protected Registers are unlocked 
//!         0: The protected Registersare locked 
//
//*****************************************************************************
int32_t DrvSYS_IsProtectedRegLocked(void)
{
	return GCR->RegLockAddr & 0x01;
}

//*****************************************************************************
//
//! \brief Get reset source from last operation 
//!
//! \param None 
//!
//! \return Reset source  
//
//*****************************************************************************
uint32_t DrvSYS_GetResetSource(void)
{
	return GCR->RST_SRC;
}

//*****************************************************************************
//
//! \brief Clear reset source by write 1 
//!
//! \param u32Src The corresponding bit of reset source 
//!
//! \return 0 
//
//*****************************************************************************
uint32_t DrvSYS_ClearResetSource(uint32_t u32Src)
{
	GCR->RST_SRC = u32Src;
	return 0;
}

//*****************************************************************************
//
//! \brief Reset coresponding IP 
//!
//! \param eIpRst The Peripheral to be reseted. It could be:
//!        E_SYS_GPIO_RST  / E_SYS_TMR0_RST / E_SYS_TMR1_RST / E_SYS_TMR2_RST 
//!        E_SYS_TMR3_RST  / E_SYS_I2C0_RST / E_SYS_I2C1_RST / E_SYS_SPI0_RST
//!        E_SYS_SPI1_RST  / E_SYS_SPI2_RST / E_SYS_PWM0_RST / E_SYS_PWM1_RST
//!        E_SYS_UART0_RST / E_SYS_UART1_RST / E_SYS_TK_RST / E_SYS_DAC_RST
//!        E_SYS_LCD_RST / E_SYS_SC0_RST / E_SYS_SC1_RST / E_SYS_USBD_RST
//!        E_SYS_ADC_RST / E_SYS_I2S_RST / E_SYS_PDMA_RST / E_SYS_EBI_RST
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_ResetIP(E_SYS_IP_RST eIpRst)
{	
	if (eIpRst == E_SYS_PDMA_RST)
	{
		UNLOCKREG();
		GCR->IPRST_CTL1_BITS.PDMA_RST = 1;
		GCR->IPRST_CTL1_BITS.PDMA_RST = 0;
		LOCKREG();
	}
	else if (eIpRst == E_SYS_EBI_RST)
	{
		UNLOCKREG();
		GCR->IPRST_CTL1_BITS.EBI_RST = 1;
		GCR->IPRST_CTL1_BITS.EBI_RST = 0;
		LOCKREG();
	}
	else
	{
		GCR->IPRST_CTL2 |= (1<<eIpRst);
		GCR->IPRST_CTL2 &= ~(1<<eIpRst);
	}
}

//*****************************************************************************
//
//! \brief Reset CPU  
//!
//! \param None 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked().
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_ResetCPU(void)
{
	UNLOCKREG();
	GCR->IPRST_CTL1_BITS.CPU_RST = 1;
    LOCKREG();
}

//*****************************************************************************
//
//! \brief Reset whole chip 
//!
//! \param None 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked().
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_ResetChip(void)
{
	UNLOCKREG();
	GCR->IPRST_CTL1_BITS.CHIP_RST = 1;
	LOCKREG();
}

//*****************************************************************************
//
//! \brief Select Brown-Out Detector threshold voltage   
//!
//! \param u8Volt 2:2.5V, 1:2.0V, 0:1.7V  
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to select BOD threshold voltage.
//!  User can check the status of Register Write-Protection Function 
//!  with DrvSYS_IsProtectedRegLocked().
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SelectBODVolt(uint8_t u8Volt)
{
    assert_param(CHECK_BOD_VOLT(u8Volt));

	UNLOCKREG();
	GCR->BODCTL |= (1 << u8Volt);
    LOCKREG();
}

//*****************************************************************************
//
//! \brief Enable Brown-Out Detector, and select BOD reset function or interrupt
//!  function. And, install call backfunction if interrupt function is selected.
//!
//! \param i32Enable 1: Enable / 0: Disable
//!
//! \param i32Flag 1: Enable BOD reset function / 0:Enable BOD interrupt function
//!  
//! \param bodcallbackFn BOD Call back function
//!
//! \param i32Volt The BOD Volt. 2:2.5V, 1:2.0V, 0:1.7V 
//!
//! \note Please make sure that Register Write-Protection Function has been
//!  disabled before using this function. User can check the status of Register
//!  Write-Protection Function with DrvSYS_IsProtectedRegLocked(). 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SetBODFunction(int32_t i32Enalbe, int32_t i32Mode, BOD_CALLBACK bodcallbackFn, int32_t i32Volt)
{
	uint32_t u32MaskBits, u32SetBits;
	
	assert_param(CHECK_BOD_ENABLE(i32Enalbe));
	assert_param(CHECK_BOD_MODE(i32Mode));
	assert_param(CHECK_BOD_VOLT(i32Volt));

	u32MaskBits = ((1 << (i32Volt + 8)) | 	// BOD INT control bit 
				   (1 << (i32Volt + 4)) |	// BOD Reset control bit 
				   (1 << (i32Volt)));		// BOD enable bit 

	NVIC_DisableIRQ(BOD_IRQn);
	
	if (i32Enalbe == 1)  {  // Enable BOD function 
		if (i32Mode == 1)  {  
			u32SetBits = ((1 << (i32Volt + 4)) | (1 << (i32Volt)));		// BOD Reset and Enable bit 
		}
		else  {
			u32SetBits = ((1 << (i32Volt + 8)) |  (1 << (i32Volt)));	// BOD Interrupt and Enable bit 
			BOD_callbackFn = bodcallbackFn;
		}
	}
	else
		u32SetBits = 0;     // Disable BOD function 
		
	UNLOCKREG();
	GCR->BODCTL = ((GCR->BODCTL & (~u32MaskBits)) | u32SetBits);
	LOCKREG();
		
	if (i32Enalbe && (!i32Mode))
		NVIC_EnableIRQ(BOD_IRQn);
		
}

//*****************************************************************************
//
//! \brief Get Brown-Out Detector state 
//!
//! \param None 
//!
//! \return BOD output status  
//!  1: the detected voltage is lower than BOD threshold voltage 
//!  0: the detected voltage is higher than BOD threshold voltage  
//
//*****************************************************************************
uint32_t DrvSYS_GetBODState(void)
{
	if (GCR->BODSTS & 0x0000000E)
		return 1;
	else
		return 0;
}

//*****************************************************************************
//
//! \brief Enable power on reset (POR) control  
//!
//! \param None 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_EnablePOR(void)
{
	UNLOCKREG();
	GCR->PORCTL = 0;
	LOCKREG();
}

//*****************************************************************************
//
//! \brief Disable power on reset (POR) control  
//!
//! \param None 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_DisablePOR(void)
{
	UNLOCKREG();
	GCR->PORCTL = 0x5AA5;
	LOCKREG();
}


//*****************************************************************************
//
//! \brief ISR to handle BOD interrupt event 
//!
//! \param None 
//!
//! \return None   
//
//*****************************************************************************
void BOD_IRQHandler(void)
{
	if (BOD_callbackFn != NULL)
		BOD_callbackFn();
}

//*****************************************************************************
//
//! \brief Enable/Disable coresponding IP clock 
//!
//! \param eIpClk The peripheral clock. It could be:
//!        E_SYS_WDT_CLK / E_SYS_RTC_CLK / E_SYS_TMR0_CLK / E_SYS_TMR1_CLK
//!        E_SYS_TMR2_CLK / E_SYS_TMR3_CLK / E_SYS_FDIV_CLK / E_SYS_I2C0_CLK 
//!        E_SYS_I2C1_CLK / E_SYS_SPI0_CLK / E_SYS_SPI1_CLK / E_SYS_SPI2_CLK
//!        E_SYS_UART0_CLK / E_SYS_UART1_CLK / E_SYS_PWM0_01_CLK / E_SYS_PWM0_23_CLK
//!        E_SYS_PWM1_01_CLK / E_SYS_PWM1_23_CLK / E_SYS_TK_CLK / E_SYS_DAC_CLK 
//!        E_SYS_LCD_CLK / E_SYS_USBD_CLK / E_SYS_ADC_CLK / E_SYS_I2S_CLK
//!        E_SYS_SC0_CLK / E_SYS_SC1_CLK / E_SYS_GPIO_CLK / E_SYS_PDMA_CLK
//!        E_SYS_ISP_CLK / E_SYS_EBI_CLK / E_SYS_SRAM_CLK / E_SYS_TICK_CLK 
//!        
//! \param i32Enable 1 : Enable / 0 : Disable
//!
//!  \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SetIPClock(E_SYS_IP_CLK eIpClk, int32_t i32Enable)
{	
	assert_param(CHECK_IPCLK_SRC(eIpClk));

	if (eIpClk >= 32)  { // AHB Clock Source 
		if (i32Enable == 1)
			CLK->AHBCLK |= (1 << (eIpClk - 32));
		else
			CLK->AHBCLK &= ~(1 << (eIpClk - 32));
	}
	else { // APB Clock Source 
		if (eIpClk == E_SYS_WDT_CLK)  {
			UNLOCKREG();
			CLK->APBCLK_BITS.WDT_EN = i32Enable;
			LOCKREG();
		}
		else  {
			if (i32Enable)
				CLK->APBCLK |= (1<<eIpClk);
			else
				CLK->APBCLK &= ~(1<<eIpClk);
		}		
	}
		
}

//*****************************************************************************
//
//! \brief This function is used to select HCLK clock source  
//!
//! \param u8ClkSrcSel The Clock Source Select. It could be:
//!        0: High Frequency External clock
//!        1: Low Frequency External clock
//!        2: PLL clock
//!        3: Low Frequency Internal clock
//!        7: High Frequency Internal clock
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return 0                          : Success 
//!         E_DRVSYS_ERR_CLKSWITCH_FAIL: Clock switch failed 
//
//*****************************************************************************
int32_t DrvSYS_SelectHCLKSource(uint8_t u8ClkSrcSel)
{
	assert_param(CHECK_HCLK_SOURCE(u8ClkSrcSel));

	UNLOCKREG();
	CLK->CLKSEL0 = u8ClkSrcSel;
	LOCKREG();

	if (CLK->CLKSTATUS_BITS.CLK_SW_FAIL)  {
		CLK->CLKSTATUS_BITS.CLK_SW_FAIL = 1;  /* Clear Clock Switch Fail Flag */
          	return E_DRVSYS_ERR_CLKSWITCH_FAIL;
    	}
		
	SystemCoreClockUpdate();
	
	return 0;
}

//*****************************************************************************
//
//! \brief This function is used to select IP clock source
//!
//! \param eIpClkSrc It could be:
//!        UART_CLK_SET / ADC_CLK_SET / PWM0_CH01_CLK_SET / PWM0_CH23_CLK_SET
//!        TMR0_CLK_SET / TMR1_CLK_SET / TK_CLK_SET  / LCD_CLK_SET  / FRQDIV_CLK_SET
//!        PWM1_CH01_CLK_SET / PWM1_CH23_CLK_SET/ TMR2_CLK_SET / TMR3_CLK_SET 
//!        I2S_CLK_SET / SC_CLK_SET
//! 
//! \param u8ClkSrcSel The peripheral Corresponding clock source. 
//!  --------------------------------------------------------------------------------------------  
//!  |  u8ClkSrcSel  |     0x00     |     0x01     |     0x02     |     0x03     |     0x07     |  
//!  --------------------------------------------------------------------------------------------  
//!  |     UART      |     HXT      |      LXT     |      PLL     |     HIRC     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!  |     ADC       |     HXT      |      LXT     |      PLL     |     HIRC     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!  | PWMx_CHxx     |     HXT      |      LXT     |      HCLK    |     HIRC     |      X       | 
//!  --------------------------------------------------------------------------------------------  
//!  |    TMR 0~3    |     HXT      |      LXT     |      LIRC    |     Ext. IN  |     HIRC     |  
//!  --------------------------------------------------------------------------------------------  
//!  |     TK        |     HXT      |      PLL     |      HIRC    |     HIRC     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!  |     LCD       |     LXT      |      LIRC    |       X      |        X     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!  |   Frequency   |     HXT      |      LXT     |      HCLK    |     HIRC     |      X       |  
//!  | Divider Output|              |              |              |              |              |  
//!  --------------------------------------------------------------------------------------------  
//!  |     I2S       |     HXT      |      PLL     |      HIRC    |     HIRC     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!  |     SC        |     HXT      |      PLL     |      HIRC    |     HIRC     |      X       |  
//!  --------------------------------------------------------------------------------------------  
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return 0:  Success 
//!       < 0:  Incorrect value  
//
//*****************************************************************************
int32_t DrvSYS_SelectIPClockSource(uint32_t u32IpClkSrc, uint8_t u8ClkSrcSel)
{
	uint32_t u32Mask, u32BitPos, u32RegNum;
	 
    assert_param(CHECK_IP_CLKSRC(u32IpClkSrc));

	u32BitPos = (u32IpClkSrc & 0x0000FF00) >> 8;
	u32Mask  = (u32IpClkSrc & 0x000000FF)  << u32BitPos;
    u32RegNum = (u32IpClkSrc & 0x00FF0000) >> 16;

    if (u32RegNum == 1)
	 	CLK->CLKSEL1 = (CLK->CLKSEL1 & (~u32Mask)) | (u8ClkSrcSel << u32BitPos);
    else
	 	CLK->CLKSEL2 = (CLK->CLKSEL1 & (~u32Mask)) | (u8ClkSrcSel << u32BitPos);

	return E_SUCCESS;
}

//*****************************************************************************
//
//! \brief This function is used to set IP divider number from the corresponding
//!  clock source The actual clock divide number is (u32value + 1) 
//!
//! \param eIpDiv It could be:
//!        HCLK_CLK_DIV / USB_CLK_DIV / UART_CLK_DIV / I2S_CLK_DIV
//!        ADC_CLK_DIV /TK_CLK_DIV / SC0_CLK_DIV / SC1_CLK_DIV
//!
//! \param i32value 0 ~ 255 for ADC, 0 ~ 15 for other IP or HCLK 
//!
//! \return 
//!       0    : Success                                                              
//!     < 0    : Incorrect value                                                    
//
//*****************************************************************************
int32_t DrvSYS_SetClockDivider(uint32_t u32IpDiv , int32_t i32value)
{
	uint32_t u32Mask, u32BitPos, u32RegNum;
	 
    assert_param(CHECK_IP_CLKDIV(u32IpDiv));

    u32BitPos = (u32IpDiv & 0x0000FF00) >> 8;
    u32Mask  = (u32IpDiv & 0x000000FF)  << u32BitPos;
	u32RegNum = (u32IpDiv & 0x00FF0000) >> 16;

	if (u32RegNum == 0)
	    CLK->CLKDIV0 = (CLK->CLKDIV0 & (~u32Mask)) | (i32value << u32BitPos);
	else
		CLK->CLKDIV1 = (CLK->CLKDIV0 & (~u32Mask)) | (i32value << u32BitPos);

     	if (u32IpDiv == HCLK_CLK_DIV)
	 	SystemCoreClockUpdate();

	return E_SUCCESS;
}

//*****************************************************************************
//
//! \brief This function is used to enable/disable internal oscillator or
//!  external crystal 
//!
//! \param eClkSrc It could be:
//!        E_SYS_HXT / E_SYS_LXT / E_SYS_HIRC / E_SYS_LIRC
//!
//! \param i32Enable 1: Enable / 0: Disable 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return 
//!           0:  Success                                                                             
//!         < 0:  Incorrect value 
//
//*****************************************************************************
int32_t DrvSYS_SetOscCtrl(E_SYS_CHIP_CLKSRC eClkSrc, int32_t i32Enable)
{	
	UNLOCKREG();
	switch(eClkSrc)
	{
		case E_SYS_HXT:  
			CLK->PWRCTL_BITS.HXT_EN = i32Enable;
			break;
		case E_SYS_LXT:
			CLK->PWRCTL_BITS.LXT_EN = i32Enable;
			break;
		case E_SYS_HIRC:
		    CLK->PWRCTL_BITS.HIRC_EN = i32Enable;
			break;
		case E_SYS_LIRC:
		    CLK->PWRCTL_BITS.LIRC_EN = i32Enable;
			break;				
		default:
			return E_DRVSYS_ERR_ARGUMENT;
	}
	LOCKREG();
	
	return E_SUCCESS;
}

//*****************************************************************************
//
//! \brief ISR to handle Power Down Wake up interrupt event 
//!
//! \param None 
//!
//! \return None  
//
//*****************************************************************************
void PWRWU_IRQHandler(void)
{
	CLK->WK_INTSTS = 1;
	if (PWRWU_callbackFn != NULL)
		PWRWU_callbackFn();	
}

//*****************************************************************************
//
//! \brief Enable or disable Power dowm Wake up interrupt function, and install
//!  call back function. And, enable or disable clock cycles delay to wait
//!  oscillator clock stable.   
//!
//! \param i32Enable 1: Enable Power dowm Wake up interrup 
//!                  0: Disable Power dowm Wake up interrup
//!  
//! \param pdwucallbackFn Power Down Wake Up Call back function
//!
//! \param i32enWUDelay 1: Enable 4096(16) clock cycle delay wait HXT(HIRC)
//!                     0: Disable 4096(16) clock cycle delay wait HXT(HIRC) 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SetPowerDownWakeUpInt(int32_t i32Enable, PWRWU_CALLBACK pdwucallbackFn, int32_t i32enWUDelay)
{

	UNLOCKREG();
	CLK->PWRCTL_BITS.PD_WK_IE = i32Enable;
	
	if (i32Enable)
	{		
		CLK->PWRCTL_BITS.WK_DLY = i32enWUDelay;
		PWRWU_callbackFn = pdwucallbackFn;
		NVIC_EnableIRQ(PDWU_IRQn);
	}
	else
	{ 
		CLK->PWRCTL_BITS.WK_DLY = 0;
		PWRWU_callbackFn = NULL;
		NVIC_DisableIRQ(PDWU_IRQn);
	}	
	LOCKREG();
}

//*****************************************************************************
//
//! \brief Enter power down function  
//!
//! \param None 
//!
//! \note Please make sure that Register Write-Protection Function has been 
//!  disabled before using this function to enable low voltage reset. 
//!  User can check the status of Register rite-Protection Function with 
//!  WDrvSYS_IsProtectedRegLocked(). 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_EnterPowerDown(void)
{	
	UNLOCKREG();
	CLK->PWRCTL_BITS.PD_EN = 1;
	LOCKREG();	
}

//*****************************************************************************
//
//! \brief This function is used to select PLL clock source 
//!
//! \param ePllSrc It could be:
//!        E_SYS_EXTERNAL_HXT: External High Frequency clock
//!        E_SYS_INTERNAL_HIRC: Internal High Frequency clock 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SelectPLLSource(E_SYS_PLL_CLKSRC ePllSrc)
{
	CLK->PLLCTL_BITS.PLL_SRC = ePllSrc;
}

//*****************************************************************************
//
//! \brief Set PLL operates in power down mode or normal mode 
//!
//! \param i32Flag 1: Power down mode / 0: Normal mode 
//!
//! \return None  
//
//*****************************************************************************
void DrvSYS_SetPLLMode(int32_t i32Flag)
{
	CLK->PLLCTL_BITS.PD = i32Flag;
}

//*****************************************************************************
//
//! \brief Get external clock frequency. The clock UNIT is in Hz. 
//!
//! \param None 
//!
//! \return The external clock frequency. The default clock is 12MHz.  
//
//*****************************************************************************
uint32_t DrvSYS_GetExtClockFreq(void)
{	
	return __XTAL;
}

//*****************************************************************************
//
//! \brief This function is used to calculate the nearest PLL frequency to fit
//!  the target PLL frequency which is defined by u32PllClk and return the
//!  relative PLL control register setting. 
//!
//! \param ePllSrc Select PLL clock source.
//!        E_DRVSYS_EXTERNAL_12M: ExternalCrystal clock 
//!        E_DRVSYS_INTERNAL_22M: Internal 22M clock
//! 
//! \param u32PllClk The target PLL clock frequency(Hz). The value is 24~120
//!  (MHz) 
//!
//! \return The PLL register setting for the target PLL clock frequency. If
//!  u32PllClk is out of range, the function will return default value 0x31120   
//
//*****************************************************************************
uint32_t DrvSYS_GetPLLContent(E_SYS_PLL_CLKSRC ePllSrc, uint32_t u32PllClk)
{
   uint32_t u32PllSrcClk, u32Register, u32Factor;
   uint32_t u32NR, u32NF, u32NO, u32IN_DV;
  	
   u32PllSrcClk = 0;
   u32Register=0;

    if (u32PllClk > FREQ_120MHZ)  
		return 0x31120;

    if (ePllSrc == E_SYS_EXTERNAL_HXT)
		u32PllSrcClk = __XTAL;
    else if (ePllSrc == E_SYS_INTERNAL_HIRC)
		u32PllSrcClk =  __IRC12M;	
 
    u32PllClk    /= 1000000;
    u32PllSrcClk /= 1000000;
    
    u32Factor = u32PllClk / u32PllSrcClk;
        
    if (u32Factor < 2)  {
		u32NO = 2; 
		u32NR = 16;
		u32IN_DV = 3;
    }
    else {
		u32NO = 1;

		if (u32Factor < 4)  {
			u32NR = 16;
			u32IN_DV = 3;
		}
		else if (u32Factor < 8) {
			u32NR = 8;
			u32IN_DV = 2;
		}
		else if (u32Factor < 16)  {
			u32NR = 4;
			u32IN_DV = 1;
		}
		else  {
			u32NR = 2;
			u32IN_DV = 0;
		}
    }

    u32NF = ((u32PllClk * 100) / u32PllSrcClk) * u32NR * u32NO;
    if ((u32NF % 100) >= 50)
		u32NF = (u32NF / 100) + 1;
    else
		u32NF = (u32NF / 100);

    u32Register = ((u32NO -1 ) <<12) | (u32IN_DV << 8) | (u32NF - 32);

    if (ePllSrc == E_SYS_INTERNAL_HIRC)
		u32Register |= 0x00020000;
    //
    // Return the nearest value 
    //
    return u32Register;
	
}

//*****************************************************************************
//
//! \brief This function is used to set PLL settings. 
//!
//! \param u32PllContent The PLL register setting for the target PLL clock
//!  frequency 
//!
//! \note User can use DrvSYS_GetPLLContent() to get proper PLL setting. 
//!  And, User can use DrvSYS_GetPLLClockFreq() to get actual PLL clock. 
//!  
//! \return None  
//
//*****************************************************************************
void DrvSYS_SetPLLContent(uint32_t u32PllContent)
{
	CLK->PLLCTL  = u32PllContent;
}


//*****************************************************************************
//
//! \brief Get PLL clock frquency. The clock UNIT is in Hz. 
//!
//! \param None 
//!
//! \return The PLL clock.  
//
//*****************************************************************************
uint32_t DrvSYS_GetPLLClockFreq(void)
{
	uint32_t u32Freq =0, u32PLLSrc;
    uint32_t u32NO, u32NR;

	if (CLK->PLLCTL_BITS.PLL_SRC == 1)
	{
		u32PLLSrc = __IRC12M;
	}
	else
	{
		u32PLLSrc = __XTAL;
	}

    u32NO = ((CLK->PLLCTL_BITS.OUT_DV)? 2: 1);

	if (CLK->PLLCTL_BITS.IN_DV == 0)
		u32NR = 2;
	else if (CLK->PLLCTL_BITS.IN_DV == 1)
		u32NR = 4;
	else if (CLK->PLLCTL_BITS.IN_DV == 2)
		u32NR = 8;
	else
		u32NR = 16;

	if (CLK->PLLCTL_BITS.PD == 1)
		u32Freq = 0;	
	else		
		u32Freq = u32PLLSrc * (CLK->PLLCTL_BITS.FB_DV+32) / u32NR / u32NO;

	return u32Freq;
}


//*****************************************************************************
//
//! \brief Get HCLK clock and the UNIT is in Hz. 
//!
//! \param None 
//!
//! \return HCLK clock frequency in Hz.  
//
//*****************************************************************************
uint32_t DrvSYS_GetHCLKFreq(void)
{
	
	uint32_t u32Freqout = 0, u32AHBDivider, u32ClkSel;

	u32ClkSel = CLK->CLKSEL0 & 0x07;

	if (u32ClkSel == 0)			// external 12MHz crystal clock 
	{
		u32Freqout = __XTAL;	
	}
	else if(u32ClkSel == 1)		// external 32KHz crystal clock  
	{
		u32Freqout = __RTC_XTAL;
	}
	else if(u32ClkSel == 2)		// PLL clock 
	{
		u32Freqout = DrvSYS_GetPLLClockFreq();
	}
	else if(u32ClkSel == 3)	    // internal 10KHz oscillator clock 
	{
	 	u32Freqout = __IRC10K;
	}
	else						// internal 12MHz oscillator clock 
	{
	 	u32Freqout = __IRC12M;
	
	}
	u32AHBDivider = CLK->CLKDIV0_BITS.HCLK_N + 1 ;
	
	return (u32Freqout/u32AHBDivider);	
}

//*****************************************************************************
//
//! \brief This could configure the PLL according to the PLL source clock and
//!  target HCLK clock.  
//!
//! \param u32Hclk The target HCLK clock frequency in Hz. The maximum of 
//!  u32Hclk is 32MHz
//!
//! \note Due to hardware limitation, the actual HCLK clock may be different 
//!  to target HCLK clock. The DrvSYS_GetPLLClockFreq() could be used to get
//!  actual PLL clock. The DrvSYS_GetHCLKFreq() could be used to get actual 
//!  HCLK clock. The DrvSYS_SetClockDivider() could be used to get lower HCLK  
//!  clock.
//!
//! \return 
//!      E_SUCCESS                    Success                                                               
//!      E_DRVSYS_ERR_OUT_OF_RANGE    The clock setting is out of range   
//
//*****************************************************************************
int32_t DrvSYS_Open(uint32_t u32Hclk)
{
	uint32_t u32PllCr;	
	volatile uint32_t delayCnt;
	E_SYS_PLL_CLKSRC ePllSrc = E_SYS_EXTERNAL_HXT;

	if (u32Hclk > FREQ_32MHZ)
	{
		return E_DRVSYS_ERR_OUT_OF_RANGE;
	}
		
	if (CLK->PLLCTL_BITS.PLL_SRC == 0)
	{
		CLK->PWRCTL_BITS.HXT_EN = 1;
		ePllSrc = E_SYS_EXTERNAL_HXT;
	}
	else
	{
		CLK->PWRCTL_BITS.HIRC_EN = 1;
		ePllSrc = E_SYS_INTERNAL_HIRC;
	}

	u32PllCr = DrvSYS_GetPLLContent(ePllSrc, u32Hclk);
	//	
	//Delay for 12M stable
    //
	for (delayCnt=0;delayCnt<10000;delayCnt++);		

	DrvSYS_SelectHCLKSource(E_SYS_SEL0_HCLK_HIRC); // select HIRC for HCLk temporarily 
	DrvSYS_SetPLLContent(u32PllCr);

	CLK->PLLCTL_BITS.PD = 0;
	//
	//Delay for PLL stable
    //
	for (delayCnt=0;delayCnt<10000;delayCnt++);
    //
	// Change HCLK clock source to be PLL. 
    //
	CLK->CLKDIV0_BITS.HCLK_N = 0;  // HCLK is the same as PLL output 
	DrvSYS_SelectHCLKSource(E_SYS_SEL0_HCLK_PLL);

	return E_SUCCESS;

}

//*****************************************************************************
//
//! \brief This function could enable main clock sources including HXT, LXT, 
//!  LIRX, HIRC and PLL. And then set HCLK clock source and divider.  
//!
//! \param sChipCfg The pointer to the structure which stores the clock 
//!  configuration of main chip. The structure elements are listed below.
//!        u8ClkEnable : E_SYS_HXT|E_SYS_LXT|E_SYS_HIRC|E_SYS_LIRC|E_SYS_PLL
//!        eHClkSrc    : E_SYS_SEL0_HCLK_XXX (HXT, LXT, PLL, LIRC or HIRC) 
//!        u8HClkDiv   : 0 ~ 15
//!        ePLLClkSrc  : E_SYS_SEL0_PLL_HXT or E_SYS_SEL0_PLL_HIRC
//!        ePLLInFreq  : E_SYS_PLLIN_4M, _8M, _12M, _15M or _24M  
//!        ePLLOutFreq : E_SYS_PLLOUT_45M, _48M, _90M, _96M, _120M or _128M 
//!
//!  \param sIPCfg The pointer to the structure which stores the clock 
//!   configuration of devices. The structure elements are listed below.
//!         u32AHBClkEn : E_SYS_HEN_GPIO |E_SYS_HEN_PDMA | E_SYS_HEN_ISP | 
//!                       E_SYS_HEN_EBI | E_SYS_HEN_SRAM | E_SYS_HEN_TICK
//!         u32APBClkEn : combination of E_SYS_PEN_XXX, the XXX can be : 
//!                       WDT, RTC, TMR0, TMR1, TMR2, TMR3, FDIV, I2C0, I2C1, SPI0,
//!                       SPI1, SPI2, UART0, UART1, PWM0_01, PWM0_23, PWM1_01,
//!                       PWM1_23, TK, DAC, LCD, USBD, ADC, I2S, SC0, SC1 
//!         u32ClkSel1  : E_SYS_SEL1_XXX_YYY, where XXX and YYY can be:
//!                       XXX:
//!                           UART, ADC, PWM0_CH01, PWM0_CH23, TMR0, TMR1, TK, LCD
//!                       YYY:
//!                           HXT, LXT, HCLK, HIRC, LIRC, EXT, PLL
//!         u32ClkSel2  : E_SYS_SEL2_XXX_YYY, where XXX and YYY can be:
//!                       XXX:
//!                           TMR2, TMR3, I2S, SC 
//!                       YYY:
//!                           HXT, LXT, HCLK, HIRC, LIRC, EXT, PLL
//!         sClkDiv     : Structure to store clock divider of devices. The elements
//!                       are listed below.
//!                       u8USBDiv  : 0 ~ 15, USBD clock divider
//!                       u8UARTDiv : 0 ~ 15, UART clock divider
//!                       u8I2SDiv  : 0 ~ 15, I2S clock divider	
//!                       u8ADCDiv  : 0 ~ 255, ADC clock divider
//!                       u8TKDiv   : 0 ~ 15, TK clock divider
//!                       u8SC0Div  : 0 ~ 15, SC0 clock divider
//!                       u8SC1Div  : 0 ~ 15, SC1 clock divider
//!
//!
//!
//!
//! \note If the second parameter is NOT NULL, this function will enable the
//!  clock and set the source/divider of peripheral devices. The device's  
//!  clock source and divider could be still changed by using 
//!  DrvSYS_SelectIPClockSource() and DrvSYS_SetClockDivider().
//!
//! \return 
//!      E_SUCCESS                    Success                                                               
//!      E_DRVSYS_ERR_CLK_UNSTABLE    The enabled clock sources are not stable                                      
//!      E_DRVSYS_ERR_PLL_CLK_SET	 The specified PLL IN/OUT setting is not supported 
//
//*****************************************************************************
int32_t DrvSYS_OpenExt(S_SYS_CHIP_CLKCFG *sChipCfg, S_SYS_IP_CLKCFG *sIPCfg)
{
	uint32_t u32ClkEn, u32ClkStb, u32PllCtl;	
	volatile uint32_t delayCnt;

	assert_param(CHECK_CLK_CONFIG(sChipCfg));              // Check input parameter 
	assert_param(CHECK_HCLK_SOURCE(sChipCfg->eHClkSrc));   // Check HCLK clock select 
    //
	// Enable chip clock source 
    //
	u32ClkEn = u32ClkStb = (sChipCfg->u8ClkEnable) & 0x0F;

	if (u32ClkEn & E_SYS_HIRC) 
		u32ClkStb = (u32ClkStb & ~E_SYS_HIRC) | 0x10;
	//
	// Set clock enabke bits and waits for clock stable
    //	
	UNLOCKREG();
	CLK->PWRCTL |= u32ClkEn; // Enable selected clocks and keep old clock sources 
	LOCKREG();
	
	for (delayCnt=0; delayCnt<100000; delayCnt++)
		if ((CLK->CLKSTATUS & u32ClkStb) == u32ClkStb)
			break;

	if (delayCnt == 100000)
		return E_DRVSYS_ERR_CLK_UNSTABLE;
    //
	// Enable PLL 
    //
	if (sChipCfg->u8ClkEnable & E_SYS_PLL)  {
        //
		// Check PLL in clock freq. 
        //
		assert_param(CHECK_PLLIN_CLK(sChipCfg->ePLLInFreq));
		assert_param(CHECK_PLLOUT_CLK(sChipCfg->ePLLOutFreq));
		
		u32PllCtl = _DrvSYS_PLL_Table[sChipCfg->ePLLInFreq][sChipCfg->ePLLOutFreq];
        //
		// Check pll clocl set 
        //
		if (u32PllCtl == 0xFFFF)
			return E_DRVSYS_ERR_PLL_CLK_SET;
        //
		// Select correct pll clock source 
        //
		if (sChipCfg->ePLLClkSrc == E_SYS_INTERNAL_HIRC)
			u32PllCtl |= 0x20000;
        //
		// Set PLL control reg and waits for PLL clock stable 
        //
		CLK->PLLCTL = u32PllCtl;

		for (delayCnt=0; delayCnt<100000; delayCnt++)
			if (CLK->CLKSTATUS & 0x04)
				break;

		if (delayCnt == 100000)
			return E_DRVSYS_ERR_CLK_UNSTABLE;		
	}
    //
	// Set HCLK divider and source 
    //
	CLK->CLKDIV0_BITS.HCLK_N = sChipCfg->u8HClkDiv;
	DrvSYS_SelectHCLKSource(sChipCfg->eHClkSrc);   // Select required clock source for HCLk 

	UNLOCKREG();
	CLK->PWRCTL = (CLK->PWRCTL & 0xFFFFFFF0) | u32ClkEn; // Disable old clock sources 
	LOCKREG();

	if (sIPCfg != NULL)  {
        //
		// Enable AHB IP clock 
        //
		CLK->AHBCLK = sIPCfg->u32AHBClkEn | 0x30; // always turn on SRAM and systetick 
        //
		// Enable APB IP clock 
        //
		if (sIPCfg->u32APBClkEn & E_SYS_PEN_WDT)  {
			UNLOCKREG();
			CLK->APBCLK = sIPCfg->u32APBClkEn;
			LOCKREG();
		}
		else
			CLK->APBCLK = sIPCfg->u32APBClkEn;
        //
		// Set clock divider 0 
        //
		CLK->CLKDIV0 |= ((sIPCfg->sClkDiv.u8USBDiv << 4) |
 						  (sIPCfg->sClkDiv.u8UARTDiv << 8) |
 						  (sIPCfg->sClkDiv.u8I2SDiv << 12) |
 						  (sIPCfg->sClkDiv.u8ADCDiv << 16) |
 						  (sIPCfg->sClkDiv.u8TKDiv << 24) |
 						  (sIPCfg->sClkDiv.u8SC0Div << 28)) ;

		CLK->CLKDIV1 = sIPCfg->sClkDiv.u8SC1Div;
        //
		// Select clock source 
        //
		CLK->CLKSEL1 = sIPCfg->u32ClkSel1;
		CLK->CLKSEL2 = sIPCfg->u32ClkSel2;

	}

	return E_SUCCESS;

}

//*****************************************************************************
//
//! \brief Enable frequency clock output and set its divider value.The output
//! frequency = Fin/2^(N+1), Fin is the input clock frequency, N is 4-bit value  
//!
//! \param i32Flag 1: Enable / 0: Disable
//!
//! \param u8Divider The divider of output frequency (0~15)
//!
//! \return 
//!                0:  Success
//!              < 0:  Incorrect value
//
//*****************************************************************************
int32_t DrvSYS_SetFreqDividerOutput(int32_t i32Flag, uint8_t u8Divider)
{
	assert_param(CHECK_FREQ_DIVIDER(u8Divider));

	if (i32Flag)
	{
		CLK->FRQDIV_BITS.FSEL = u8Divider;
	}
	CLK->FRQDIV_BITS.FDIV_EN = i32Flag; 		
	DrvSYS_SetIPClock(E_SYS_FDIV_CLK, i32Flag);

	return E_SUCCESS;
}

//*****************************************************************************
//
//! \brief Use the SysTick to generate the delay time and the UNIT is in us. The
//!  SysTick clock source is from HCLK.
//!
//! \param us Delay time. The Max value is 335000
//!
//! \return None
//
//*****************************************************************************
void DrvSYS_Delay(uint32_t us)
{
	SysTick->LOAD = (us * ((SystemCoreClock + 500000) / 1000000));
	SysTick->VAL  = (0x00);
      SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;

    /* Waiting for down-count to zero */
    while((SysTick->CTRL & (1 << 16)) == 0);
}

//*****************************************************************************
//
//! \brief This function is used to monitor if the chip clock source is stable
//!  or not
//!
//! \param eClkSrc E_SYS_HXT / E_SYS_LXT / E_SYS_HIRC / E_SYS_LIRC / E_SYS_PLL
//!
//! \return
//!     0:  Clock source is not stable or not enabled
//!     1:  Clock source is stable
//!   < 0:  Incorrect value
//
//*****************************************************************************
int32_t DrvSYS_GetChipClockSourceStatus(E_SYS_CHIP_CLKSRC eClkSrc)
{
	int32_t status;

	switch(eClkSrc)
	{
		case E_SYS_HXT:  
			status = CLK->CLKSTATUS_BITS.HXT_STB;
			break;
		case E_SYS_LXT:
			status = CLK->CLKSTATUS_BITS.LXT_STB;
			break;
		case E_SYS_HIRC:
			status = CLK->CLKSTATUS_BITS.HIRC_STB;
			break;
		case E_SYS_LIRC:
			status = CLK->CLKSTATUS_BITS.LIRC_STB;
			break;				
		case E_SYS_PLL:
			status = CLK->CLKSTATUS_BITS.PLL_STB;
			break;
		default:
			return E_DRVSYS_ERR_ARGUMENT;
	}

	return status;		
}

//*****************************************************************************
//
//! \brief This function is used to get if switch target clock is successful
//!  or not when software switch system clock source.
//!
//! \param None
//!
//! \return
//!     0:  Clock switch success
//!     1:  Clock switch fail
//
//*****************************************************************************
uint32_t DrvSYS_GetClockSwitchStatus(void)
{	
	return CLK->CLKSTATUS_BITS.CLK_SW_FAIL;
}

//*****************************************************************************
//
//! \brief This function is used to clear the Clock Switch Fail Flag
//!
//! \param None
//!
//! \return None
//
//*****************************************************************************
void DrvSYS_ClearClockSwitchStatus(void)
{
	 CLK->CLKSTATUS_BITS.CLK_SW_FAIL = 1;
}

//*****************************************************************************
//
//! \brief This function is used to enable auto-trim fucntion
//!
//! \param eTrimClk The parameter to configure IRC-Trim clock. It could be:
//!        E_SYS_TRIM_110592M / E_SYS_TRIM_12M / E_SYS_TRIM_12288M
//!
//! \param pfncallback A function pointer for callback function 
//!
//! \return  0:  success
//!        < 0:  32K clock not enable
//
//*****************************************************************************
int32_t DrvSYS_EnableAutoTrim(E_SYS_IRC_CLKSEL eTrimClk, DRVSYS_IRC_CALLBACK pfncallback)
{
	if (CLK->PWRCTL_BITS.LXT_EN != 1)	// Not enable the 32K 
		return E_DRVSYS_ERR_32K_CLK_FAIL;

	g_trimClk = eTrimClk;

	GCR->IRCTRIMCTL_BITS.TRIM_LOOP = 3;

	_pfIrcCallback = (void (*)(uint32_t))pfncallback;

	GCR->IRCTRIMIER = 0x6; // Enable trim-fail and 32K-error interrupt
	NVIC_EnableIRQ(HIRC_IRQn);

	GCR->IRCTRIMCTL_BITS.TRIM_SEL = eTrimClk;

	return E_SUCCESS;
}

//*****************************************************************************
//
//! \brief This function is used to disable auto-trim fucntion
//!
//! \param None 
//!
//! \return  None
//
//*****************************************************************************
void DrvSYS_DisableAutoTrim(void)
{
	GCR->IRCTRIMCTL_BITS.TRIM_SEL = 0;
    //
    // Disable trim-fail and 32K-error interrupt
    //
	GCR->IRCTRIMIER = 0; 
	_pfIrcCallback = NULL;
	NVIC_DisableIRQ(HIRC_IRQn);
}
